/*
 * @(#)Xfsim.java        2003/01/01
 *
 * This file is part of Xfuzzy 3.0, a design environment for fuzzy logic
 * based systems.
 *
 * (c) 2000 IMSE-CNM. The authors may be contacted by the email address:
 *                    xfuzzy-team@imse.cnm.es
 *
 * Xfuzzy is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by
 * the Free Software Foundation.
 *
 * Xfuzzy is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * for more details.
 */

package xfuzzy.xfsim.view;

import xfuzzy.*;
import xfuzzy.lang.*;
import xfuzzy.util.*;
import xfuzzy.xfsim.model.*;
import xfuzzy.xfsim.view.listener.*;

import javax.swing.*;
import java.awt.*;
import java.io.*;

/**
 * Ventana principal de la aplicaci�n "xfsim" de simulaci�n de sistemas
 * difusos.
 * 
 * @author Francisco Jos� Moreno Velo
 *
 */
public class Xfsim extends JFrame implements IXfsimFrame {

	//----------------------------------------------------------------------------//
	//                            COSTANTES PRIVADAS                              //
	//----------------------------------------------------------------------------//

	/**
	 * C�digo asociado a la clase serializable
	 */
	private static final long serialVersionUID = 95505666603056L;
	
	//----------------------------------------------------------------------------//
	//                            MIEMBROS PRIVADOS                               //
	//----------------------------------------------------------------------------//

	/**
	 * Ventana principal del entorno
	 */
	private Xfuzzy xfuzzy;
	
	/**
	 * Sistema difuso a simular
	 */
	private Specification spec;
	
	/**
	 * Hilo del proceso de simulaci�n 
	 */
	private XfsimProcess process;
	
	/**
	 * Configuraci�n del proceso de simulaci�n
	 */
	private XfsimConfig config;
	
	/**
	 * Estado de la simulaci�n
	 */
	private int running = 0;
	
	/**
	 * Campos de texto para configurar el modelo de planta
	 */
	private XTextForm modelform[];
	
	/**
	 * Campos para mostrar la evoluci�n de la simulaci�n
	 * (iteraciones y tiempo)
	 */
	private XTextForm evolform[];
	
	/**
	 * Campos para mostrar la salida de la planta en cada instante
	 */
	private XTextForm plantform[];
	
	/**
	 * Campos para mostrar la salida del sistema difuso en cada instante
	 */
	private XTextForm fuzzyform[];
	
	/**
	 * Barra de botones
	 */
	private XCommandForm commandform;
	
	/**
	 * Lista de salidas a generar
	 */
	private XList outputlist;
	
	//----------------------------------------------------------------------------//
	//                                CONSTRUCTOR                                 //
	//----------------------------------------------------------------------------//

	/**
	 * Constructor
	 */
	public Xfsim(Xfuzzy xfuzzy, Specification spec) {
		super("Xfsim");
		this.xfuzzy = xfuzzy;
		this.spec = spec;
		this.config = new XfsimConfig();
		build();
		refreshConfig();
	}
	
	//----------------------------------------------------------------------------//
	//                             M�TODOS P�BLICOS                               //
	//----------------------------------------------------------------------------//
	
	/**
	 * Devuelve la referencia al sistema difuso a simular
	 */
	public Specification getSpecification() {
		return this.spec;
	}
	
	/**
	 * Devuelve la configuraci�n del proceso de simulaci�n
	 */
	public XfsimConfig getConfiguration() {
		return this.config;
	}
	
	/**
	 * Devuelve el estado de ejecuci�n de la simulaci�n
	 */
	public int getStatus() {
		return this.running;
	}
	
	/**
	 * Verifica si se est� realizando un proceso de simulaci�n
	 */
	public boolean isSimulating() {
		return (process != null && process.isAlive());
	}
	
	/**
	 * Coloca la ventana en la posici�n deseada sobre la pantalla
	 */
	public void setLocation() {
		if(xfuzzy != null) {
			Point loc = xfuzzy.frame.getLocationOnScreen();
			loc.x += 40; loc.y += 200;
			setLocation(loc);
		} else {
			Dimension frame = getSize();
			Dimension screen = getToolkit().getScreenSize();
			setLocation((screen.width - frame.width)/2,(screen.height - frame.height)/2);
		}
	}
	
	/**
	 * Actualiza los valores de la zona de configuraci�n
	 */
	public void refreshConfig() {
		PlantModel plant = config.getPlantModel();
		if(plant != null)
			modelform[0].setText(plant.getClass().getName());
		else modelform[0].setText("");
		
		modelform[1].setText(config.getLimit().toString());
		
		if(config.getPlantInit() == null) modelform[2].setText("default");
		else modelform[2].setText("configured");
		
		outputlist.setListData(config.getOutputs());
		
		commandform.setEnabled(2,config.isConfigured());
		repaint();
	}
		
	/**
	 * Actualiza los valores de los campos de estado
	 */
	public void setStatus(XfsimStatus status){
		evolform[0].setText(""+status.getIteration());
		evolform[1].setText(redondeo(status.getTime())+" ms");
		double[] ptst = status.getFuzzyInputs();
		double[] fzst = status.getFuzzyOutputs();
		for(int i=0; i<fzst.length; i++) fuzzyform[i].setText(redondeo(fzst[i]));
		for(int i=0; i<ptst.length; i++) plantform[i].setText(redondeo(ptst[i]));
		setSensitive(status.getStatus());
	}
	
	/**
	 * Actualiza el estado de la barra de comandos	
	 */
	public void setSensitive(int kind) {
		switch(kind) {
		case XfsimStatus.READY_TO_RUN:
			commandform.setLabel(2,"Run");
			commandform.setEnabled(0,true);
			commandform.setEnabled(1,true);
			commandform.setEnabled(2,config.isConfigured());
			commandform.setEnabled(3,true);
			commandform.setEnabled(4,true);
			running = XfsimStatus.READY_TO_RUN;
			return;
		case XfsimStatus.RUNNING:
			commandform.setLabel(2,"Stop");
			commandform.setEnabled(0,false);
			commandform.setEnabled(1,false);
			commandform.setEnabled(3,false);
			commandform.setEnabled(4,false);
			running = XfsimStatus.RUNNING;
			return;
		case XfsimStatus.STOPPED:
			commandform.setLabel(2,"Continue");
			commandform.setEnabled(0,true);
			commandform.setEnabled(1,true);
			commandform.setEnabled(3,true);
			commandform.setEnabled(4,true);
			running = XfsimStatus.STOPPED;
			return;
		case XfsimStatus.FINISHED:
			commandform.setLabel(2,"Finished");
			commandform.setEnabled(0,true);
			commandform.setEnabled(1,true);
			commandform.setEnabled(2,false);
			commandform.setEnabled(3,true);
			commandform.setEnabled(4,true);
			running = XfsimStatus.FINISHED;
			return;
		}
	}

	/**
	 * Carga la configuraci�n desde un fichero externo
	 */
	public void load() {	
		File wdir = xfuzzy.getWorkingDirectory();
		XFileChooser chooser = new XFileChooser(wdir,XFileChooser.LOAD);
		chooser.addFileFilter(".xml","Xfuzzy Configuration Files");
		int returnVal = chooser.showDialog();
		if(returnVal != XFileChooser.SELECTED) return;
		File file = chooser.getSelectedFile();
		
		XmlParser parser = new XmlParser();
		if(!parser.parseXML(file)) {
			xfuzzy.log(parser.getErrorMessage());
			Toolkit.getDefaultToolkit().beep();			
			return;
		}
		XfsimConfig conf = parser.getXfsimConfig();
		if(conf != null) {
			config = conf;
			refreshConfig();
		} else {
			xfuzzy.log(parser.getErrorMessage());
			Toolkit.getDefaultToolkit().beep();
		}
	}
	
	/**
	 * Guarda la configuraci�n en un fichero externo
	 */
	public void save() {		
		File wdir = xfuzzy.getWorkingDirectory();
		XFileChooser chooser = new XFileChooser(wdir,XFileChooser.SAVE);
		chooser.addFileFilter(".xml","Xfuzzy Configuration Files");
		int returnVal = chooser.showDialog();
		if(returnVal != XFileChooser.SELECTED) return;
		File file = chooser.getSelectedFile();
		
		boolean append = false;
		if(file.exists()) {
			String question[] = new String[3];
			question[0] = "File "+file.getName()+" already exists.";
			question[1] = "Do you want to overwrite this file?";
			question[2] = "(Otherwise the code will be append at the end of the file)";
			int result = XDialog.showYNQuestion(this, question);
			if(result == XDialog.CANCEL) return;
			if(result == XDialog.NO) append = true;
		}
		config.save(file,spec.getName(),append);
	}
	
	/**
	 * Ejecuta el proceso de simulacion
	 */
	public void run() {
		if(running == XfsimStatus.READY_TO_RUN) {
			setSensitive(XfsimStatus.RUNNING);
			if(process == null || !process.isAlive())  process = new XfsimProcess(this);
		} else if(running == XfsimStatus.RUNNING) {
			process.sendStop();
			setSensitive(XfsimStatus.STOPPED);
		} else if(running == XfsimStatus.STOPPED){
			process.sendContinue();
			setSensitive(XfsimStatus.RUNNING);
		}
	} 
	
	/**
	 * Destruye el proceso de simulaci�n e inicializa los campos
	 */
	public void reload() {
		if(process != null && process.isAlive()) {
			process.sendFinish();
			try { process.join(); } catch(InterruptedException ex) {}
		}
		this.process = null;
		for(int i=0; i<evolform.length; i++) evolform[i].setText("");
		for(int i=0; i<plantform.length; i++) plantform[i].setText("");
		for(int i=0; i<fuzzyform.length; i++) fuzzyform[i].setText("");
		refreshConfig();
		setSensitive(XfsimStatus.READY_TO_RUN);
	}
	
	/**
	 * Cierra la ventana de simulaci�n
	 */
	public void close() {
		if(process != null && process.isAlive()) {
			running = XfsimStatus.FINISHED;
			process.sendFinish();
			
			try { process.join(); } catch(InterruptedException ex) {}
		}
		if(xfuzzy == null) System.exit(0);
		else setVisible(false);
	}
	
	/**
	 * Selecciona la clase que desarrolla el modelo de planta
	 */
	public void plant() {
		File wdir = null;
		if(config.getPlantFile() != null) wdir = config.getPlantFile();
		else if(xfuzzy != null) wdir = xfuzzy.getWorkingDirectory();
		else wdir = new File(System.getProperty("user.dir"));
		
		XFileChooser chooser = new XFileChooser(wdir,XFileChooser.SELECT);
		chooser.setTitle("Select plant model");
		chooser.addFileFilter(".class","Java binary files");
		int val = chooser.showDialog();
		if(val == XFileChooser.CANCELED) return;
		
		File file = chooser.getSelectedFile();
		try {
			config.setPlantModel(file);
			refreshConfig();
		}
		catch(Exception ex) { System.out.println(ex.toString()); }
	}
	
	/**
	 * Selecciona los valores iniciales
	 */
	public void init() {
		XfsimInitDialog dialog = new XfsimInitDialog(this);
		dialog.setVisible(true);
		refreshConfig();
	}
	
	/**
	 * Selecciona las condiciones de termino
	 */
	public void limit() {
		XfsimLimitDialog dialog = new XfsimLimitDialog(this);
		dialog.setVisible(true);
		refreshConfig();
	}
	
	/**
	 * Crea un nuevo fichero de almacenamiento hist�rico
	 */
	public void insertLogfile() {
		Variable[] input = this.spec.getSystemModule().getInputs();
		Variable[] output = this.spec.getSystemModule().getOutputs();
		
		XfsimLog logfile = new XfsimLog(input.length,output.length);
		XfsimLogDialog dialog = new XfsimLogDialog(this,logfile);
		dialog.setVisible(true);
		if(dialog.getResult()) {
			config.addOutput(logfile);
			refreshConfig();
		}
	}
	
	/**
	 * Crea una nueva representaci�n gr�fica
	 */
	public void insertPlot() {
		XfsimPlot plot = new XfsimPlot("n","o0",0);
		XfsimPlotDialog dialog = new XfsimPlotDialog(this,plot);
		dialog.setVisible(true);
		if(dialog.getResult()) {
			config.addOutput(plot);
			refreshConfig();
		}
	}
	
	/**
	 * A�ade una salida al proceso de simulaci�n
	 */
	public void insertOutput() {
		XfsimOutputDialog dialog = new XfsimOutputDialog(this);
		dialog.setVisible(true);
	}
	
	/**
	 * Elimina una salida del proceso de simulaci�n
	 */
	public void removeOutput() {
		try {
			XfsimLog logfile = (XfsimLog) outputlist.getSelectedValue(); 
			config.removeOutput(logfile);
			refreshConfig();
		} catch(Exception ex) {}
		
		try {
			XfsimPlot plot = (XfsimPlot) outputlist.getSelectedValue();
			config.removeOutput(plot);
			refreshConfig();
		} catch(Exception ex) {}
	}
	
	/**
	 * Edita una salida del proceso de simulaci�n
	 */
	public void editOutput() {
		try {
			XfsimLog logfile = (XfsimLog) outputlist.getSelectedValue();
			XfsimLog tmp = logfile.duplicate();
			XfsimLogDialog dialog = new XfsimLogDialog(this,tmp);
			dialog.setVisible(true);
			if(!dialog.getResult()) return;
			logfile.copyFrom(tmp);
			refreshConfig();
		} catch(Exception ex) {}
		
		try {
			XfsimPlot plot = (XfsimPlot) outputlist.getSelectedValue();
			XfsimPlot tmp = plot.duplicate();
			XfsimPlotDialog dialog = new XfsimPlotDialog(this,tmp);
			dialog.setVisible(true);
			if(!dialog.getResult()) return;
			plot.copyFrom(tmp);
			refreshConfig();
		} catch(Exception ex) {}
	}
		
	//----------------------------------------------------------------------------//
	//                             M�TODOS PRIVADOS                               //
	//----------------------------------------------------------------------------//
	
	/**
	 * Crea la ventana
	 */
	private void build() {
		XfsimActionListener listener = new XfsimActionListener(this);
		
		String modellb[] =
		{"Plant model","Simulation limit","Initial values"};
		String modelcm[] = { "Plant", "Limit", "Init" };
		
		modelform = new XTextForm[3];
		for(int i=0; i<3; i++) {
			modelform[i] = new XTextForm(modellb[i],listener);
			modelform[i].setActionCommand(modelcm[i]);
			modelform[i].setEditable(false);
			modelform[i].setFieldWidth(200);
		}
		XTextForm.setWidth(modelform);
		
		outputlist = new XList("Simulation output");
		outputlist.addMouseListener(new XfsimMouseListener(this));
		outputlist.addKeyListener(new XfsimKeyListener(this));
		
		Box modelbox = new Box(BoxLayout.Y_AXIS);
		modelbox.add(new XLabel("Simulation model"));
		for(int i=0; i<modelform.length; i++) modelbox.add(modelform[i]);
		modelbox.add(Box.createVerticalStrut(5));
		modelbox.add(outputlist);
		
		String evollb[] = {"Iteration (_n)","Time (_t)"};
		evolform = new XTextForm[2];
		for(int i=0; i<evolform.length; i++) {
			evolform[i] = new XTextForm(evollb[i]);
			evolform[i].setEditable(false);
			evolform[i].setFieldWidth(200);
		}
		
		Variable input[] = spec.getSystemModule().getInputs();
		plantform = new XTextForm[input.length];
		for(int i=0; i<plantform.length; i++) {
			plantform[i] = new XTextForm(input[i].getName());
			plantform[i].setEditable(false);
			plantform[i].setFieldWidth(200);
		}
		
		Variable output[] = spec.getSystemModule().getOutputs();
		fuzzyform = new XTextForm[output.length];
		for(int i=0; i<fuzzyform.length; i++) {
			fuzzyform[i] = new XTextForm(output[i].getName());
			fuzzyform[i].setEditable(false);
			fuzzyform[i].setFieldWidth(200);
		}
		
		int evollength = evolform.length+plantform.length+fuzzyform.length;
		XTextForm tform[] = new XTextForm[evollength];
		for(int i=0; i<evolform.length; i++) tform[i] = evolform[i];
		for(int i=0; i<plantform.length; i++) tform[evolform.length+i] = plantform[i];
		for(int i=0; i<fuzzyform.length; i++)
			tform[evolform.length+plantform.length+i] = fuzzyform[i];
		XTextForm.setWidth(tform);
		
		Box evolbox = new Box(BoxLayout.Y_AXIS);
		evolbox.add(new XLabel("Evolution"));
		for(int i=0; i<evolform.length; i++) evolbox.add(evolform[i]);
		evolbox.add(new XLabel("Plant state"));
		for(int i=0; i<plantform.length; i++) evolbox.add(plantform[i]);
		evolbox.add(new XLabel("Fuzzy system output"));
		for(int i=0; i<fuzzyform.length; i++) evolbox.add(fuzzyform[i]);
		evolbox.add(Box.createVerticalGlue());
		
		Box body =  new Box(BoxLayout.X_AXIS);
		body.add(modelbox);
		body.add(Box.createHorizontalStrut(5));
		body.add(evolbox);
		
		String formlb[] = {"Load", "Save", "Run", "Reload", "Close"};
		commandform = new XCommandForm(formlb,formlb,listener);
		commandform.setCommandWidth(120);
		commandform.block();
		
		Container content = getContentPane();
		content.setLayout(new BoxLayout(content,BoxLayout.Y_AXIS));
		content.add(new XLabel("Simulation for specification "+spec.getName()));
		content.add(body);
		content.add(commandform);
		
		setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
		setIconImage( XfuzzyIcons.xfuzzy.getImage() );
		addWindowListener(new XfsimWindowListener(this));
		pack();
		setLocation();
	}
	
	/**
	 * Devuelve la cadena que representa al n�mero con 6 digitos
	 */
	private String redondeo(double dd) {
		String data = ""+dd;
		char[] cdat = data.toCharArray();
		int i;
		for(i=0; i<cdat.length; i++) if(cdat[i]>'0' && cdat[i]<='9') break;
		for(int j=0; j<6 && i<cdat.length; j++,i++)
			if(cdat[i]=='e' || cdat[i]=='E') break;
		StringBuffer buf = new StringBuffer(data.substring(0,i));
		int e = data.indexOf("E");
		if(e == -1) e = data.indexOf("e");
		if(e != -1) buf.append(data.substring(e));
		return buf.toString();
	}	
	
}
